[ver1.5新機能]対象のモデルのデータ型や制約が定義通りであるか保証できる「Contracts」を試してみた
さがらです。
先日、dbt-coreのver1.5がリリースされました。
dbt-core ver1.5の新機能として、「Contracts」がリリースされました。この機能を試してみたので、本記事で試した内容をまとめてみます。
Contractsとは
一言でいうと、対象のモデルのデータ型や制約が定義通りであるか保証できる機能です。
dbtは基本的に定義されたSELECT文が返す結果に応じて、カラム名とデータ型を自動で決めてくれますが、ソースデータの仕様変更などの影響でデータ型が勝手に変わってしまうことなどが起こり得ます。
勝手にデータ型が変わると、後続のdbtのモデルが上手く動かなくなったり、BIツールで対象のモデルから出力されたテーブルを参照するダッシュボードが壊れてしまったり、いろんなトラブルに繋がる可能性があります。
そんなときに役立つのが、今回の新機能「Contracts」です!
dbt上で事前に各モデルが出力するカラムに対してdata_type
やconstraints
を定義しておくことで、データ型がちゃんと定義どおりか、制約も問題ないか(使用するDB・DWHで強制できるもの限定)、をdbt run/build実行時に確認できるようになります。
試してみた
検証環境
- DWH:Snowflake
- dbt Cloud
- dbt-Core:ver1.5 ※Environmentより設定
検証内容
ある中間モデルintermediate_customers.sql
について、出力されるテーブルのデータ型を保証するために、Contractsを定義してみます。
intermediate_customers.sql
select customer_id, first_name, last_name, first_order, number_of_orders from {{ ref("int_customer_order_history_joined") }}
これを一度実行しSnowflake上で確認すると、下図のようにデータ型が定義されていました。
Contractsの定義
上述のintermediate_customers.sql
に対してyamlファイルintermediate_customers.yml
を作成し、以下のように定義します。
ポイントは3つあります。
contract:
では、enforced: true
とすることで、後述のdata_type
とconstraints
を満たしているか確認するようになるdata_type:
では、使用しているDB・DWHのデータ型の名称を用いて、どのデータ型であることを満たさないといけないか、定義するconstraints
では、使用しているDB・DWHの制約を用いて、どの制約を満たさないといけないか、定義する- ※注意事項:DWHの仕様上で強制できないものは強制できないため別途dbtのテスト機能を用いて保証する必要があります(例えば、Snowflakeでは
not null
以外は強制できない)。ただ、constraints
で定義された制約はDDL文に含まれるようになります。
- ※注意事項:DWHの仕様上で強制できないものは強制できないため別途dbtのテスト機能を用いて保証する必要があります(例えば、Snowflakeでは
models: - name: intermediate_customers config: contract: enforced: true columns: - name: customer_id data_type: varchar constraints: - type: not_null - type: primary_key tests: - unique - not_null - name: first_name data_type: varchar - name: last_name data_type: varchar - name: first_order data_type: date - name: number_of_orders data_type: number
constraintsを定義したことによる効果の確認
上述の内容でContractsを定義した上で、一度buildしてみます。
すると、build実行時に「SnowflakeではPrimary Keyは強制していないよ!」とWARNINGが表示されました。Snowflakeにおいてはconstraints
は強制できるnot null
制約についてのみ、実際のデータを確認するようです。
実際にSnowflakeで作られたDDL文を見ると、constraints
で定義したprimary key
とnot null
がどちらもDDL文に含まれていることがわかります。Contractsでconstraints
を定義すれば、dbt_constraintsパッケージをインストールしなくても制約をdbtから適用できるので便利ですね!
あえてデータ型をContractsで定義したdata_typeと違うものにしてみる
続いて、data_type
がちゃんと機能しているのかを検証するため、あえてdata_type
と異なるデータ型を出力するようにモデルの定義を変更してみます。
具体的には、customer_id
はdata_type: varchar
としてますが、あえてCASTしてint
として出力されるようにしてみます。
select customer_id::int as customer_id, first_name, last_name, first_order, number_of_orders from {{ ref("int_customer_order_history_joined") }}
この状態でbuildすると、下図のようにThis model has an enforced contract that failed.
Please ensure the name, data_type, and number of columns in your contract match the columns in your model's definition.
というエラーメッセージと、具体的にどのカラムにどういった問題があるのかをまとめた表が返ってきます。
このように事前にcontracts
を定義しておくことで、data_type
が定義どおりでない場合はエラーを出力してくれることがわかりました!
最後に
dbt-coreのver1.5の新機能「contracts」を試してみました。
あるモデルから出力されるテーブルにおいてデータ型や制約を保証して、後続のモデルやBIツールなどに影響が出ないようにしたい、そんなときに使える機能だと思います!